<?xml version="1.0" encoding="utf-8"?>
<!--
	Assignment #5 for CS4406 Computer Graphics
	Copyright © 2019 Alex Yst <mailto:copyright@y.st>

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program. If not, see <https://www.gnu.org./licenses/>.
-->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<script type="text/javascript" src="https://getfirebug.com/firebug-lite-debug.js"/>
		<meta name="description" content="CS4406 Computer Graphics - Assignment #5"/>
		<meta charset="utf-8"/>
		<title>Assignment #5 for CS4406 Computer Graphics</title>
		<style>
			#container {
				background: #000000;
				width: 100%;
				height: 100%;
			}
		</style>
		<style id="jsbin-css"/>
	</head>
	<body>
		<div id="container"/>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"/> 
		<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/85/three.min.js"/>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.5/dat.gui.min.js"/>
		<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"/>
		<script src="http://uopeopleweb.com/js/math.js"/>
		<script type="text/javascript">
// set the scene size
var WIDTH = 600, HEIGHT = 600;

// set some camera attributes
var VIEW_ANGLE = 45, ASPECT = WIDTH / HEIGHT, NEAR = 1, FAR = 1000;

// get the DOM element to attach to
var $container = $(&apos;#container&apos;);

// create a WebGL renderer, camera, and a scene
// NOTE: for the assignment in Unit 4 where you need to use a texture, or in any other assignment where a texture is required 
// you should deactivate the Detector and use ONLY the CanvasRenderer.  There are some issues in using what are called Cross Domain images for 
// for textures.   You can get more details by looking up WebGL and CORS using Google search.  I have included some code below that will 
// get around this issue that you can use. 
var renderer = new THREE.WebGLRenderer();

var scene = new THREE.Scene();
var clock = new THREE.Clock();
var camera = new THREE.PerspectiveCamera(VIEW_ANGLE,ASPECT,NEAR,FAR);

// the camera starts at 0,0,0 so pull it back for some assignments you may need to adjust this value
// some distance to make the scene visible properly
camera.position.z = 30;    	

// add the camera to the scene
scene.add(camera)

// set up the camera controls.  Please keep in mind that what this does is move the entire scene around.
// because the entire scene is moving the position of the camera and lights in relation to objects within 
// the scene doesn&apos;t change so the lighting on the surface of the object(s) will not change either
var cameraControls = new THREE.OrbitControls(camera, renderer.domElement);
cameraControls.addEventListener(&apos;mousemove&apos;, renderer);

// start the renderer
renderer.setSize(WIDTH, HEIGHT);

// attach the render-supplied DOM element
$container.append(renderer.domElement);

// ----------------------------------------------------------------------------------------
//  Example of Code that you can adapt to get around the issue of Cross-Domain issues and 
//  CORS restrictions using textures.  We have this problem when using jsbin.com as a
//  development environment becuase we cannot load texture images to the local server.
//  Rather we need to pull them from location using a web URL.  You can use the images 
//  that we have put on the uopeopleweb.com site along with the following code (modified 
//  of course for your particular program)
//
//  Notice that what this code does is create a new Texture object called Texture1 and loads
//	the texture image into the object.  
//  
//	var Texture1 = new THREE.Texture();
//	var loader = new THREE.ImageLoader();
//
//	loader.addEventListener( &apos;load&apos;, function ( event ) {
//			Texture1.image = event.content;
//			Texture1.needsUpdate = true;
//	} );
//
//	loader.load( &apos;http://uopeopleweb.com/js/paper.jpg&apos; );
	
// ----------------------------------------------------------------------------------------
//  END OF THE STANDARD CODE FOR THE ASSIGNMENT
//  Following this is where you must place your own custom code to complete the assignment
// ----------------------------------------------------------------------------------------

// First, we add a PointLight, which is a sort of directional light
// that shines in every direction from a fixed point. Without a
// directional light, we can&apos;t have any shadows.
var PointLight = new THREE.PointLight(0xffffff);

// Speaking of which, the light should be set to cast shadows.
PointLight.castShadow = true;

// We can set the intensity of the shadows, too. 0 means there&apos;s no
// shadow, 1 means the shadow is completely black. 0.5 seems like a
// good value.
PointLight.shadowDarkness = 0.5;

// Let&apos;s put the PointLight up above the camera.
PointLight.position.y = 8;
camera.add(PointLight);

// Using only the PointLight, we can&apos;t see the bottoms of the objects.
// They&apos;re completely in shadow. I guess that&apos;s fine, but adding an
// ambient light corrects the problem, so let&apos;s do that. If we keep the
// ambient light low enough, we can still see the light and shadow,
// while if we keep it high enough, we can see even the part of the
// geometry that&apos;s in shadow.
var AmbientLight = new THREE.AmbientLight(0x1f1f1f);
scene.add(AmbientLight);

// We set the light to cast shadows earlier, but that&apos;s not enough. We
// also need to set the renderer to look for shadows.
renderer.shadowMapEnabled = true;

// We need a geometry object to work with to build into a plane to cast
// shadows on.
var backdrop_geometry = new THREE.Geometry();

// We could create a really big triangle, that extends way beyond the
// edges of our scene, allowing us to do this using only a single
// space. However, a square would be easier to calculate, not to
// mention that it would be semantically closer to what we&apos;re after. A
// square has four corners, so we&apos;ll need four vertices.
backdrop_geometry.vertices.push(new THREE.Vector3(20, 20, 0)); 
backdrop_geometry.vertices.push(new THREE.Vector3(20, -20, 0)); 
backdrop_geometry.vertices.push(new THREE.Vector3(-20, -20, 0)); 
backdrop_geometry.vertices.push(new THREE.Vector3(-20, 20, 0)); 

// Here, we use indices into the vertex list we just pushed vertices
// onto to define the bounding points of our triangles. Wait,
// triangles? I thought we were building a square! Well in actuality,
// WebGL is unable to draw real squares. Instead, we have to define
// triangles that share vertices in order to create the illusion of a
// square out of two triangles.
//
// Note that the order we specify the vertices in matters. It
// determines which side of the triangle is the front, so we want to
// either specify the vertices in clockwise order to make both
// triangles face backwards or specify the vertices in
// counter-clockwise order to make both triangles face forward. I chose
// clockwise, so the triangles face toward the camera.
backdrop_geometry.faces.push(new THREE.Face3(3, 1, 0));  
backdrop_geometry.faces.push(new THREE.Face3(3, 2, 1));  

// When you build your own geometry like we did with the background, it
// doesn&apos;t automatically have the normals calculated like when you use
// the built-in geometries. Without the normals calculated, the object
// (in this case, the background plane) won&apos;t react to directional
// lights whatsoever, and will appear to be completely in shadow all
// the time. Let&apos;s correct that problem by calculating the normals.
backdrop_geometry.computeFaceNormals();
backdrop_geometry.computeVertexNormals();

// According to the three.js documentation, a MeshLambertMaterial is a
// material that doesn&apos;t shine. You don&apos;t get highlights based on where
// the light is compared to the material. In other words, the material
// is more matte. That&apos;ll work just fine for our backdrop.
var backdrop_material = new THREE.MeshLambertMaterial({
// Let&apos;s make the backdrop white.
	color:0xffffff,
});

// Now, we build a mesh out of the two triangles and the material.
var backdrop  = new THREE.Mesh(backdrop_geometry, backdrop_material);   

// The backdrop needs to be set to accept shadows.
backdrop.receiveShadow = true;

// Let&apos;s set the backdrop behind everything.
backdrop.position.z = -40;

// And we add the backdrop to the scene, fixed to the camera so the
// backdrop doesn&apos;t rotate with the rest of the scene.
camera.add(backdrop);

// I tried to set these values really low, so fewer polygons would need
// to render. Setting them *too* low resulted in the spheres not
// looking like spheres much at all. Using sixteen for both values
// seems to be enough to make the sphere look good though.
var segments = 16, rings = 16;

// For our molecular model, let&apos;s first define the central carbon that
// links the four hydrogens together.
var carbon = new THREE.Mesh(new THREE.SphereGeometry(3, segments, rings), new THREE.MeshPhongMaterial({
// We were instructed to make the carbon red. 0xff0000 is the most
// vibrant red available to us.
	color: 0xff0000,
// We need to define the colour that&apos;ll be used to simulate the
// reflection of the local light source. For the most-significant
// effect, we set this to pure white, but I toned it down by making it
// a very light red instead.
	specular: 0xff8888,
}));

// let&apos;s add the carbon to the scene. It&apos;ll actually be the only object
// aside from the ambient light and the camera that we add directly to
// the scene. Everything else will be added to the carbon (or a child)
// or the camera.
scene.add(carbon);

// All four hydrogens will use the same material, so to avoid defining
// that material four times, we can store it in a variable.
var hydrogen_material = new THREE.MeshPhongMaterial({
// We&apos;ve been asked to make the hydrogens blue. 0x0000ff is the most
// vibrant blue we have available to us.
	color: 0x0000ff,
// We need to define the colour that&apos;ll be used to simulate the
// reflection of the local light source. For the most-significant
// effect, we set this to pure white. Instead, I set it to a light blue
// to soften the effect.
	specular: 0x8888ff,
});

// All four bonds share a material too, so again, we&apos;ll store that
// material in a variable.
var bond_material = new THREE.MeshPhongMaterial({
// We&apos;ve been asked to make the bonds white. To make the highlighting
// work better, I didn&apos;t set it to the brightest white available, but
// instead, a slightly dimmer white.
	color: 0xcccccc,
// We need to define the colour that&apos;ll be used to simulate the
// reflection of the local light source. For the most-significant
// effect, we set this to pure white.
	specular: 0xffffff,
});

// The bonds all share the same geometry, so let&apos;s set that in a
// variable.
var bond_geometry = new THREE.CylinderGeometry(
// This is the radius of the circle on one end of the cylinder. We want
// a diameter of one, so a radius of one half gets us what we&apos;re after.
	0.5,
// three.js allows you to set the radius of the circle on the other end
// of the &quot;cylinder&quot; separately from the radius of the first circle. Of
// course, if the two aren&apos;t equal, it&apos;s not a real cylinder, as
// defined by the mathematical term. Let&apos;s set it equal to the first.
	0.5,
// This is the length of the cylinder. It should be long enough to
// slightly clip into the two spheres it connects, but should not be
// long enough to extend out the other side of said spheres.
	4,
// This is the number of faces around the edge of the cylinder. If you
// have too many, it wastes system resources to render, but if you have
// too few, it doesn&apos;t look like a cylinder. Eight seems to be an
// alright value here.
	8,
// This is the number of segments from one circular end of the cylinder
// to the other. Why you would set this value to anything besides one
// is beyond me. All that would do is waste system resources when
// rendering.
	1,
// Set this to true to leave the ends open. Leaving them open saves a
// few polygons, and we can&apos;t see the ends anyway, because they&apos;re
// inside the spheres.
	true
);

// We&apos;ll reuse the hydrogen geometry again. So you guessed it, another
// variable.
var hydrogen_geometry = new THREE.SphereGeometry(1.75, segments, rings);

// If we tried to set the position of the cylinder and translate it by
// setting bondX.position and bondX.rotation, three.js would always
// rotate before translating. Normally, this provides the expected
// behaviour that you&apos;d want. You normally want to rotate before
// translating to get a reasonable result. In this case though, we need
// to rotate about the origin, not about the objects&apos; own centres, so
// we either need to translate before rotating or we need to do a bunch
// of complex maths after rotating so we can figure out where to
// translate to. I chose the easy way: translating first. Here, we
// translate geometry within its own coordinate space, instead of in
// the world coordinate space, so it rotates correctly in the world
// coordinate space.
bond_geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, 4.5, 0));

// Let&apos;s do the same thing with the hydrogen geometry so that rotates
// correctly as well.
hydrogen_geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, 8, 0));

// Let&apos;s add four hydrogen atoms.
var hydrogen0 = new THREE.Mesh(hydrogen_geometry, hydrogen_material);
var hydrogen1 = new THREE.Mesh(hydrogen_geometry, hydrogen_material);
var hydrogen2 = new THREE.Mesh(hydrogen_geometry, hydrogen_material);
var hydrogen3 = new THREE.Mesh(hydrogen_geometry, hydrogen_material);

// If we attach the four hydrogen atoms to the carbon atom, moving the
// carbon atom will automatically move the hydrogen atoms in sync.
// Let&apos;s do that.
carbon.add(hydrogen0);
carbon.add(hydrogen1);
carbon.add(hydrogen2);
carbon.add(hydrogen3);

// Each hydrogen atom needs a bond between it and the central carbon
// atom. Let&apos;s define the meshes for those bonds.
var bond0 = new THREE.Mesh(bond_geometry, bond_material);
var bond1 = new THREE.Mesh(bond_geometry, bond_material);
var bond2 = new THREE.Mesh(bond_geometry, bond_material);
var bond3 = new THREE.Mesh(bond_geometry, bond_material);

// Intuitively, we&apos;d attach the bods to the carbon atom, then attach
// the hydrogen atoms to the bonds. However, if we instead attach the
// hydrogen atoms directly to the carbon atom and attach the bonds to
// the hydrogen atoms, we can change the angles of the hydrogen atoms
// and the bonds will move to perfectly connect them. So let&apos;s now
// attach the bonds to the hydrogen atoms.
hydrogen0.add(bond0)
hydrogen1.add(bond1)
hydrogen2.add(bond2)
hydrogen3.add(bond3)

// Hydrogen 0 is fine where it is. The other three need to be rotated
// into place though.
hydrogen1.rotation.z = 2;
hydrogen2.rotation.z = 2;
hydrogen3.rotation.z = 2;
hydrogen2.rotation.y = Math.PI * 2 / 3;
hydrogen3.rotation.y = Math.PI * 4 / 3;

// All of these objects should cast shadows.
carbon.castShadow = true;
bond0.castShadow = true;
bond1.castShadow = true;
bond2.castShadow = true;
bond3.castShadow = true;
hydrogen0.castShadow = true;
hydrogen1.castShadow = true;
hydrogen2.castShadow = true;
hydrogen3.castShadow = true;

// ----------------------------------------------------------------------------------------
// END OF YOUR CUSTOM CODE FOR THE ASSIGNMENT
// The rendering functions that follow are standard and can be used for this assignment.
// You are welcome to customize them or create your own if you desire, however, you can
// simply use the code provided.
//

// Standard functions for rendering the scene.  Notice how we have the animate function
// which submits a call to requestAnimationFrame to call animate.   This creates a loop
// that will render the scene again whenever something within the scene changes.
function animate() {
	requestAnimationFrame(animate);
	render();
}

function render() {
	cameraControls.update();
	renderer.render(scene, camera);
}
animate();
		</script>
	</body>
</html>
